/* floatfuncs.c - floating-point formatting and scanning library */

/*
DESCRIPTION
This library provides the floating-point I/O formatting and scanning
support routines.

The floating-point formatting and scanning support routines are not
directly callable; they are connected to call-outs in the printf()/scanf()
family of functions in fioLib.  This is done dynamically by the routine
floatInit(), which is called by the root task, usrRoot(), in usrConfig.c
when the configuration macro INCLUDE_FLOATING_POINT is defined.
If this option is omitted (i.e., floatInit() is not called), floating-point
format specifications in printf() and sscanf() are not supported.
*/


#include "string.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "math.h"
#include "limits.h"
#include "stdarg.h"
#include "floatio.h"


/* macros */

#define	to_digit(c)	((c) - '0')
#define is_digit(c)	((unsigned)to_digit(c) <= 9)
#define	to_char(n)	((n) + '0')


/* forward static functions */

 int 	fformat (va_list *pVaList, int precision, BOOL doAlt, 
		             int fmtSym, BOOL *pDoSign, char *pBufStart, 
		             char *pBufEnd);
 int 	fscan (int *pReturn, int returnSpec, int fieldwidth,
		           FUNCPTR getRtn, int getArg, int *pCh, int *pNchars);
LOCAL int 	cvt (double number, int prec, BOOL doAlt, int fmtch,
	       	     BOOL *pDoSign, char *startp, char *endp);
LOCAL char *	exponentCvt (char *p, int exp, int fmtch);
LOCAL char *	roundCvt (double fract, int *exp, char *start, char *end, 
			  char ch, BOOL *pDoSign);


/*******************************************************************************
*
* fformat - format arg for output
*
* This routine converts from a floating-point value to an ASCII representation.
* The <fmtSym> parameter indicates the format type desired; the actual work
* is done in cvt.
*
* RETURNS: number of characters placed in buffer
*/

 int fformat 
    (
    va_list *	pVaList,	/* vararg list */
    int		precision,	/* precision */
    BOOL	doAlt, 		/* doAlt boolean */
    int		fmtSym,		/* format symbol */
    BOOL *	pDoSign, 	/* where to fill in doSign result */
    char *	pBufStart,	/* buffer start */
    char *	pBufEnd		/* buffer end */
    )
    {
    double dbl = (double) va_arg (*pVaList, double);
    *pDoSign = FALSE;				/* assume no sign needed */

    if (isinf(dbl)) 				/* infinite? */
	{
	strcpy (pBufStart, "Inf");		/* fill in the string */
	if (dbl < 0.0)				/* less than 0.0 */
	{
	    *pDoSign = TRUE;			/* we need a sign */
	}

	return (-3);				/* length will be three */
	}

    if (isnan(dbl)) 				/* not a number? */
	{
	strcpy (pBufStart, "NaN");		/* fill in the string */
	return (-3);				/* length will be three */
	}

    return (cvt (dbl, precision, doAlt, fmtSym, pDoSign, pBufStart, pBufEnd));
    }

/******************************************************************************
* 
* cvt - helper function for fformat
* 
* RETURNS: 
*/

LOCAL int cvt
    (
    double	number,
    FAST int	prec,
    BOOL	doAlt,
    int		fmtch,
    BOOL *	pDoSign,
    char *	startp,
    char *	endp
    )
    {
    FAST char *	p;
    FAST char *	t;
    FAST double fract;
    int 	dotrim;
    int 	expcnt;
    int 	gformat;
	int		nonZeroInt=FALSE;
    double 	integer;
    double	tmp;

    dotrim = expcnt = gformat = 0;

    if (number < 0) 
	{
	number = -number;
	*pDoSign = TRUE;
	}
    else
	*pDoSign = FALSE;

    fract = modf(number, &integer);

	if(integer!=(double)0.0)
		nonZeroInt=TRUE;

    t = ++startp;			/* get an extra slot for rounding. */

    /* get integer portion of number; put into the end of the buffer; the
     * .01 is added for modf(356.0 / 10, &integer) returning .59999999...
     */

    for (p = endp - 1; integer; ++expcnt) 
	{
	tmp  = modf(integer / 10, &integer);
	*p-- = to_char((int)((tmp + .01) * 10));
	}

    switch (fmtch) 
	{
	case 'f':
		/* reverse integer into beginning of buffer */
		if (expcnt)
		    for (; ++p < endp; *t++ = *p);
		else
		    *t++ = '0';

		/* if precision required or alternate flag set, add in a
		 * decimal point.
		 */

		if (prec || doAlt)
		    *t++ = '.';

		/* if requires more precision and some fraction left */

		if (fract) 
		    {
		    if (prec)
			do 
			    {
			    fract = modf(fract * 10, &tmp);
			    *t++  = to_char((int)tmp);
			    } while (--prec && fract);

		    if (fract)
			startp = roundCvt (fract, (int *)NULL, startp, t - 1,
				       (char)0, pDoSign);
		    }

		for (; prec--; *t++ = '0')
		    ;
		break;

	case 'e':
	case 'E':
eformat:	if (expcnt) 
		    {
		    *t++ = *++p;

		    if (prec || doAlt)
			*t++ = '.';

		    /* if requires more precision and some integer left */

		    for (; prec && ++p < endp; --prec)
			*t++ = *p;

		    /* if done precision and more of the integer component,
		     * round using it; adjust fract so we don't re-round
		     * later.
		     */

		    if (!prec && ++p < endp)
			{
			fract  = 0;
			startp = roundCvt((double)0, &expcnt, startp, t - 1, *p,
				       pDoSign);
			}

		    --expcnt;	/* adjust expcnt for dig. in front of decimal */
		    }

		/* until first fractional digit, decrement exponent */

		else if (fract) 
		    {
		    /* adjust expcnt for digit in front of decimal */

		    for (expcnt = -1;; --expcnt) 
			{
			fract = modf(fract * 10, &tmp);
			if (tmp)
			    break;
			}

		    *t++ = to_char((int)tmp);

		    if (prec || doAlt)
			*t++ = '.';
		    }
		else 
		    {
		    *t++ = '0';
		    if (prec || doAlt)
			*t++ = '.';
		    }

		/* if requires more precision and some fraction left */
		if (fract) 
		    {
		    if (prec)
			do 
			    {
			    fract = modf(fract * 10, &tmp);
			    *t++ = to_char((int)tmp);
			    } while (--prec && fract);

		    if (fract)
			startp = roundCvt(fract, &expcnt, startp, t - 1,(char)0,
				          pDoSign);
		    }

		for (; prec--; *t++ = '0')	/* if requires more precision */
		    ;

		/* unless alternate flag, trim any g/G format trailing 0's */

		if (gformat && !doAlt) 
		    {
		    while (t > startp && *--t == '0')
			;

		    if (*t == '.')
			--t;
		    ++t;
		    }

		t = exponentCvt (t, expcnt, fmtch);
		break;

	case 'g':
	case 'G':

		/* a precision of 0 is treated as a precision of 1. */

		if (!prec)
		    ++prec;

		/*
		 * ``The style used depends on the value converted; style e
		 * will be used only if the exponent resulting from the
		 * conversion is less than -4 or greater than the precision.''
		 *	-- ANSI X3J11
		 */

		if ((expcnt > prec) || ((!expcnt && fract && fract) < 0.0001))
		    {
		    /*
		     * g/G format counts "significant digits, not digits of
		     * precision; for the e/E format, this just causes an
		     * off-by-one problem, i.e. g/G considers the digit
		     * before the decimal point significant and e/E doesn't
		     * count it as precision.
		     */
		    --prec;
		    fmtch  -= 2;		/* G->E, g->e */
		    gformat = 1;
		    goto eformat;
		    }

		/*
		 * reverse integer into beginning of buffer,
		 * note, decrement precision
		 */

		if (expcnt)
		    for (; ++p < endp; *t++ = *p, --prec)
			;
		else
		    *t++ = '0';

		/*
		 * if precision required or alternate flag set, add in a
		 * decimal point.  If no digits yet, add in leading 0.
		 */

		if (prec || doAlt) 
		    {
		    dotrim = 1;
		    *t++ = '.';
		    }
		else
		    dotrim = 0;

		/* if requires more precision and some fraction left */

		if (fract) 
		    {
		    if (prec) 
			{

			/*
			 * If there is a zero integer portion, so we can't count any
             * of the leading zeros as significant.  Roll 'em on out until
			 * we get to the first non-zero one.
			 */
			if (!nonZeroInt)
				{
				do 
			    	{
			    	fract = modf(fract * 10, &tmp);
			    	*t++ = to_char((int)tmp);
			    	} while(!tmp);
				prec--;
				}

			/*
             * Now add on a number of digits equal to our precision.
             */
			while (prec && fract) 
			    {
			    fract = modf(fract * 10, &tmp);
			    *t++ = to_char((int)tmp);
				prec--;
			    }
			}
		    if (fract)
			startp = roundCvt (fract, (int *)NULL, startp, t - 1,
				           (char)0, pDoSign);
		    }

		/* alternate format, adds 0's for precision, else trim 0's */

		if (doAlt)
	    	for (; prec--; *t++ = '0')
	        	;
		else if (dotrim) 
		    {
		    while (t > startp && *--t == '0')
			;

		    if (*t != '.')
			++t;
		    }

	default :
		break;
	}

    return (t - startp);
    }

/******************************************************************************
*
* roundCvt - helper function for fformat
* 
* RETURNS: 
*/

LOCAL char *roundCvt
    (
    double	fract,
    int *	exp,
    FAST char *	start,
    FAST char *	end,
    char 	ch,
    BOOL *	pDoSign
    )
    {
    double tmp;

    if (fract)
	(void)modf(fract * 10, &tmp);
    else
	tmp = to_digit(ch);
    if (tmp > 4)
	{
	for (;; --end) 
	    {
	    if (*end == '.')
		    --end;
	    if (++*end <= '9')
		    break;
	    *end = '0';
	    if (end == start) 
		{
		if (exp) 
		    {				/* e/E; increment exponent */
		    *end = '1';
		    ++*exp;
		    }
		else 
		    {				/* f; add extra digit */
		    *--end = '1';
		    --start;
		    }
		break;
		}
	    }
	}

    /* ``"%.3f", (double)-0.0004'' gives you a negative 0. */

    else if (*pDoSign)
	for (;; --end) 
	    {
	    if (*end == '.')
		--end;

	    if (*end != '0')
		break;

	    if (end == start)
		*pDoSign = FALSE;
	    }

    return (start);
    }

/******************************************************************************
*
* exponentCvt - helper function for fformat
* 
* RETURNS: 
*/

LOCAL char *exponentCvt
    (
    FAST char *	p,
    FAST int 	exp,
    int 	fmtch
    )
    {
    FAST char *t;
    char expbuf[MAXEXP];

    *p++ = fmtch;
    if (exp < 0) 
	{
	exp = -exp;
	*p++ = '-';
	}
    else
	*p++ = '+';

    t = expbuf + MAXEXP;

    if (exp > 9) 
	{
	do 
	    {
	    *--t = to_char(exp % 10);
	    } while ((exp /= 10) > 9);

	*--t = to_char(exp);

	for (; t < expbuf + MAXEXP; *p++ = *t++)
	    ;
	}
    else 
	{
	*p++ = '0';
	*p++ = to_char(exp);
	}

    return (p);
    }

/*******************************************************************************
*
* fscan - scan ASCII input to a floating-point number
*
* This routine scans the ASCII input into a floating-point number.
*
* [+-]<digs>.<digs>e[+-]<digs>
*
* RETURNS: TRUE if successful, FALSE if unsuccessful
*/

 int fscan
    (
    FAST int *  pReturn,
    int         returnSpec,     /* 0, 'l', 'L' */
    int         fieldwidth,
    FAST FUNCPTR getRtn,
    FAST int    getArg,
    int *       pCh,
    int *       pNchars
    )
    {
#define DBL_DIG			16			/* float.h */

#define GET_CHAR(ch, ix)	((ix)++, (ch) = getRtn (getArg))

    LOCAL double power10 [] =
	    {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8};
    LOCAL double posExpPower10 [] =
	    {1e1, 1e2, 1e4, 1e8, 1e16, 1e32, 1e64, 1e128, 1e256, 0};

    FAST int	ch	  = *pCh;	/* current char */
    FAST int	ix	  = 0;		/* number of chars consumed */
    FAST long	long1	  = 0;		/* 1st part of integer representation */
    FAST long	long2	  = 0;		/* 2nd part of integer representation */
    int		ndigs1	  = 0;		/* num of digits in long1 */
    int		ndigs2	  = 0;		/* num of digits in long2 */
    int		ndigsPrec = 0;		/* num of digits of precision total */
    BOOL	fracPart  = FALSE;	/* TRUE = doing digits after '.' */
    int		ndigsFrac = 0;		/* number of digits after '.' */
    int		exp	  = 0;		/* exponent */
    double	num;			/* double representation of number */
    BOOL	negnum = FALSE;		/* number is negative */
    BOOL	negexp = FALSE;		/* exponent is negative */
    double *	pExpPower10;		/* current exponent bit multiplier */

    /* check for sign */

    if (ix < fieldwidth)
	{
	if ((negnum = ((char)ch == '-')) || ((char)ch == '+'))
	    GET_CHAR (ch, ix);
	}

    /* scan integer and fraction parts */

    for (; (ch != EOF) && (ix < fieldwidth); GET_CHAR (ch, ix))
	{
	if (!fracPart && ch == '.')
	    {
	    fracPart = TRUE;
	    continue;
	    }

	if ((ch < '0') || (ch > '9'))
	    break;

	ch -= '0';		/* turn ch into digit */

	if (fracPart)		/* count digits after '.' */
	    ndigsFrac++;

	if ((ndigsPrec != 0) || (ch != 0))	/* skip leading 0s */
	    {
	    ndigsPrec++;

	    /* check if another digit will fit in long1 */

	    if (long1 < ((INT_MAX - 9) / 10))
		{
		long1 = 10 * long1 + ch;
		ndigs1++;
		}
	    else if (ndigsPrec <= DBL_DIG)
		{
		long2 = 10 * long2 + ch;
		ndigs2++;
		}
	    }
	}


    /* scan for explicit exponent */

    if ((ix < fieldwidth) && (ch == 'e' || ch == 'E'))
	{
	GET_CHAR (ch, ix);

	/* skip over sign */

	if (ix < fieldwidth)
	    {
	    if ((negexp = (ch == '-')) || (ch == '+'))
		GET_CHAR (ch, ix);
	    }

	for (; (ch != EOF) && (ix < fieldwidth); GET_CHAR (ch, ix))
	    {
	    if ((ch < '0') || (ch > '9'))
		break;

	    exp = 10 * exp + (ch - '0');
	    }
	}


    /* check that we scanned at least one character */

    if (ix == 0)
	return (FALSE);

    /* put significant digits into double representation */

    num = long1;
    if (ndigs2 != 0)
	num = num * power10 [ndigs2] + long2;


    /* apply exponent to number; total effective exponent =
     *   + number of digits of precision that were scanned but not
     *     represented in the number (ndigsPrec - ndigs1 - ndigs2)
     *   - number of digits scanned after the '.' (ndigsFrac)
     *   + the scanned explicit exponent
     */

    exp = (ndigsPrec - ndigs1 - ndigs2) - ndigsFrac + (negexp ? -exp : exp);

    if ((negexp = (exp < 0)))
	exp = -exp;

    pExpPower10 = posExpPower10;

    while ((exp != 0) && (*pExpPower10 != 0))
	{
	if (exp & 1)
	    {
	    if (negexp) 
	    	num /= *pExpPower10;
	    else
	    	num *= *pExpPower10;
	    }
	exp >>= 1;
	pExpPower10++;
	}

    if (exp != 0)
	{
	/* exponent too big */

	if (negexp)
	    num = 0;
	else
	    {
	    /* Infinity */
	    ((DOUBLE *) &num)->ldat.l1 = 0x7ff00000;
	    ((DOUBLE *) &num)->ldat.l2 = 0;
	    }
	}


    /* return value to caller */

    if (negnum)
	num = -num;

    if (pReturn != NULL)
	{
	switch (returnSpec)
	    {
	    case 'l': * (double *) pReturn = num;	break;
	    default:  * (float *) pReturn = num;	break;
	    }
	}

    *pCh = ch;
    *pNchars += ix;

    return (ix != 0);
    }

